package com.bj58.groupbuy.action.util;

import java.lang.ref.WeakReference;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class LRUCache<K,V> {
	
    private LinkedHashMap<K,V> map;
    private int capacity;
    private ExpirationProcessor exProcess;
	private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

	private static class ExpirationProcessor extends Thread {
        
        private Map<Object,Long> expirations;
        private long expireTime;
        private WeakReference<LRUCache> ref;
        
        private boolean stopRequired = false;
        
        public ExpirationProcessor(LRUCache map, long expire){
            super("LRU Expiration Processor");
            super.setDaemon(true);
            expireTime = expire;
            expirations = new ConcurrentHashMap<Object,Long>(); 
            ref = new WeakReference<LRUCache>(map);
        }
        
        public void run(){
            while(true){
                try {
                    Thread.sleep(10*1000);		// check for every 10 seconds
                } catch (InterruptedException e) {
                }
                LRUCache map = ref.get();
                if(stopRequired || (map == null)){
                    return;
                }
                long currentTime = System.currentTimeMillis();
                Lock lock = map.readWriteLock.readLock();
                try {
                	lock.lock();
					Iterator<Map.Entry<Object,Long>> itr = expirations.entrySet().iterator();
					while((itr != null)&&(itr.hasNext())){
					    Map.Entry<Object,Long> entry = itr.next();
					    if(entry == null){
					        continue;
					    }
					    Long lastAccess = entry.getValue();
					    if(lastAccess != null){
					        if((currentTime - lastAccess.longValue()) > expireTime){
					        	
					        	lock.unlock();
					        	Lock writelock = map.readWriteLock.writeLock();
					        	writelock.lock();
					            try {
					                Object key = entry.getKey();
					                Object value = map.remove(key);
					                expirations.remove(key);
					            } catch ( Throwable t){
					            } finally{
					            	lock.lock();
					            	writelock.unlock();
					            }
					        }
					    }
					}
				} finally {
					if(lock != null){
						lock.unlock();
					}
				}
            }
        }

        /* (non-Javadoc)
         * @see java.util.Map#put(K, V)
         */
        public Long put(Object key, Long value) {
            return expirations.put(key, value);
        }

        /* (non-Javadoc)
         * @see java.util.Map#remove(java.lang.Object)
         */
        public Long remove(Object key) {
            return expirations.remove(key);
        }
    }

    /**
     * 
     */
    public LRUCache(int size) {
        this(size,0);
    }
    
    /**
     * 
     * @param capacity
     * @param expired , expiration time in minute. member will be evicted if it has not been accessed
     * for longer time that expired. 0 : expiration will not apply in this map
     */
    public LRUCache(int size,int expired) {
        if(size <= 0){
            throw new IllegalArgumentException("Size should larger than 0");
        }
        map = new LinkedHashMap<K,V>(size);
        this.capacity = size;
        if(expired > 0){
            exProcess = new ExpirationProcessor(this,expired*60*1000L);
            exProcess.start();
        }
    }
    
    public boolean containsKey(K key){
    	Lock lock = readWriteLock.readLock();
    	lock.lock();
    	try{
	    	return map.containsKey(key);
    	}finally{
    		lock.unlock();
    	}
    }
    
    public V get(K key){
    	Lock readLock = readWriteLock.readLock();
    	readLock.lock();
    	try {
			if(map.containsKey(key)){
				Lock writeLock = readWriteLock.writeLock();
				readLock.unlock();
				writeLock.lock();				
				try {
					map.put(key,map.remove(key));		// Most-Recent used is always in last
					if(exProcess != null){
					    exProcess.put(key,System.currentTimeMillis());
					}
				}finally {
					readLock.lock();
					writeLock.unlock();
				}
				return map.get(key);
			}
			return null;
		} finally {
			readLock.unlock();
		}
    }
    
    public V peek(K key){
    	Lock readLock = readWriteLock.readLock();
    	readLock.lock();
    	try {
				return map.get(key);
		} finally {
			readLock.unlock();
		}
    }

    public void put(K key, V value){
		if(key == null){
			throw new IllegalArgumentException("Key object cannot be null.");
		}
    	Lock writeLock = readWriteLock.writeLock();
    	writeLock.lock();
		try {
			if(map.containsKey(key)){
				map.remove(key);
			}else{
			    if(capacity == 0){
			        return;
			    }
				if(map.size()==capacity){	// reach the limit of key list
					K rmKey = map.keySet().iterator().next();	// remove the eldest(LRU) one
					V rmVal = map.remove(rmKey);
			        if(exProcess != null){
			            exProcess.remove(rmKey);
			        }
				}
			    if(exProcess != null){
			        exProcess.put(key,System.currentTimeMillis());
			    }
			}
			map.put(key,value);
		} finally {
			writeLock.unlock();
		}
    }
    
    public int size(){
    	Lock readLock = readWriteLock.readLock();
    	readLock.lock();
    	try{
	    	return map.size();
    	}finally{
    		readLock.unlock();
    	}
    }
    
    public void setCapacity(int newSize){
    	Lock lock = readWriteLock.writeLock();
    	lock.lock();
        try {
			this.capacity = newSize;
			if(map.size() > capacity){
				int count = map.size()-capacity;
				for(Iterator<K> itr = map.keySet().iterator();itr.hasNext()&&(count > 0);count--){  // shrink the map
				    K rmKey = itr.next();   // remove the eldest(LRU) one
				    V rmVal = map.get(rmKey);
				    itr.remove();
				    if(exProcess != null){
				        exProcess.remove(rmKey);
				    }
				}
			}
		} finally {
			lock.unlock();
		}
    }

    
    public V remove(K key) {
    	Lock lock = readWriteLock.writeLock();
    	lock.lock();
        try {
	        if(exProcess != null){
	            exProcess.remove(key);
	        }
	        return map.remove(key);
        }finally{
        	lock.unlock();
        }
    }
    
    
    public List<K> keys(){
    	Lock lock = readWriteLock.readLock();
    	lock.lock();
    	try{
            return new LinkedList<K>(map.keySet());
       	}finally{
    		lock.unlock();
    	}
    }
   
    /**
     * @return Returns the capacity.
     */
    public int getCapacity() {
    	Lock lock = readWriteLock.readLock();
    	lock.lock();
    	try{
            return capacity;
       	}finally{
    		lock.unlock();
    	}
    }
}
